package com.ajview.core.security.filter;

import com.ajview.common.core.domain.bo.LoginUser;
import com.ajview.common.core.domain.enums.ResultCodeEnum;
import com.ajview.common.core.exception.auth.TokenCheckException;
import com.ajview.common.utils.SecurityUtils;
import com.ajview.common.utils.StringUtils;
import com.ajview.common.utils.spring.SpringUtils;
import com.ajview.core.security.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.LocaleResolver;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author ZhangJunJie
 * @Date 2022-04-28
 * <p>
 * 概要：JWT Token 验证拦截器
 */
@Component
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Resource
    private TokenService tokenService;
    @Resource
    private HandlerExceptionResolver handlerExceptionResolver;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String token = tokenService.getToken(request);
            // 设置LocaleContextHolder 用于设置全局语言，设置国际化默认语言
            LocaleContextHolder.setLocale(SpringUtils.getBean("localeResolver", LocaleResolver.class).resolveLocale(request));
            // 没有token不进行认证处理
            if (!StringUtils.isEmpty(token)) {
                LoginUser loginUser = tokenService.getLoginUser(token);
                log.info("从token中获取到用户信息，当前用户id为{},对象为{}", loginUser != null ? loginUser.getUserId() : "空", ObjectUtils.isEmpty(loginUser) ? "空" : "有值");

                // token合法返回，但是没有获取到用户信息，则说明token已过期
                if (!ObjectUtils.isEmpty(loginUser) && ObjectUtils.isEmpty(SecurityUtils.getAuthentication())) {
                    // 判断是否需要重新刷新redis中存储的令牌信息
                    tokenService.verifyToken(loginUser);
                    log.info("存储当前登录人到SecurityContextHolder");
                    /*
                     * 因为关闭了session使用jwt进行认证，所以SecurityContextHolder.getContext()每次请求完毕后都会清空，所以需要每次在认证之前从token取出用户信息进行存储
                     */
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
                    authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityUtils.setAuthentication(authenticationToken);
                } else {
                    log.warn("当前认证token为{}，已经超时了。", token);
                    // token 超时，此处主动抛出自定义异常，由外层try catch 捕获转移到controller中，使Spring全局异常处理能捕获到该异常
                    throw new TokenCheckException(ResultCodeEnum.LOGIN_TIMEOUT);
                }
            }
        } catch (TokenCheckException e) {
            //捕获异常并转移到controller中，使Spring全局异常处理能捕获到该异常
            handlerExceptionResolver.resolveException(request, response, null, e);
            return;
        }

        filterChain.doFilter(request, response);

    }
}
